frontpage-content/diaries/rust/05 - Structs.adoc

211 lines
5.6 KiB
Plaintext

:experimental:
:docdatetime: 2022-08-10T17:04:53+02:00
= Structs
https://doc.rust-lang.org/book/ch05-00-structs.html[Link zum Buch]
== Was sind Structs
Structs kennt man ja aus C/C++.
Man kann es (denke ich) auch mit JavaScript Objekten vergleichen.
In Structs gruppiert man zusammengehöriges Zeug und hat so eine Art Pseudo-OOP.
Man kann damit neue Datentypen machen.
== How to
=== "Normale" Structs
[source, rust]
----
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("{}", user1.email);
user1.email = String::from("anotheremail@example.com");
}
----
Hinweis: Es können nicht einzelne Felder mutable sein, sondern wenn dann immer das ganze Struct.
==== Dinge wie in Javascript
Wenn die Variable heißt wie das Feld, kann man auch statt `email: email` einfach nur `email` schreiben.
Wenn man ein neues Struct aus einem alten mit Updates erstellen will, geht das auch mit einer Art Spread-Parameter:
[source, rust]
----
let user2 = User {
email: String::from("another@example.com"),
..user1
};
----
`..user1` *muss* als letztes kommen und füllt dann alle bisher nicht gesetzten Felder.
Außerdem ist das etwas tricky:
Wenn die Daten, die von `user1` zu `user2` übertragen werden, gemoved werden (sprich: keine primitiven Datentypen sind), dann ist `user1` danach ungültig.
Hätten wir jetzt auch noch einen neuen `username` gesetzt (auch ein String) und nur `active` und `sign_in_count` übertragen, wäre `user1` noch gültig.
=== Tupel Structs
[source, rust]
----
struct RGBColor(u8, u8, u8);
fn main() {
let black = Color(0, 0, 0)
}
----
Sind nutzbar wie Tupel (destrucuture und `.index` zum Zugriff auf Werte), allerdings eben ein eigener Typ.
=== Unit-Like Structs
[source, rust]
----
struct AlwaysEqual;
----
Ein Struct muss keine Felder haben.
Das Buch meint, man könnte für diesen Datentypen jetzt noch Traits implementieren, aber davon habe ich noch keine Ahnung.
Nur dann macht diese Art von Struct irgendwie Sinn.
== Ownership der Felder
Im ersten Beispiel wird `String` satt `&str` genutzt.
Wir wollen am besten im Struct keine Referenzen, oder es müssen "named lifetime parameter" sein, etwas das wir erst später lernen.
Der Compiler wird sonst streiken.
== Das erste Mal Traits
Im Buch folgt ein Beispielprogramm für ein Struct, das ein Rechteck abbildet.
Wir wollten das ganze printen (mit `{}` als Platzhalter), allerdings implementiert Das Rechteck nicht `std::fmt::Display`.
Das scheint eine Art `toString()` für Rust zu sein.
Es gibt aber noch eine andere Möglichkeit und das haben wir schonmal für Tupel genutzt:
`{:?}` als Platzhalter (bzw. `{:#?}` für pretty print).
Dafür brauchen wir aber das Trait `Debug`.
Zum Glück scheint das aber einfach zu implementieren sein, es muss nur implementiert werden.
Der Compiler schlägt uns zwei Varianten vor:
1. `#[derive(Debug)]` über der Definition des Structs
2. `impl Debug for Rectangle` manuell
Jetzt können wir Variablen dieses Typs printen und es zeigt uns Datentyp und Felder an.
Alternativ kann man auch das Makro `dbg!(...)` nutzen.
Das wird dann auf `stderr` geprintet.
Man kann sogar ein Statement da rein packen (also zum Beispiel `30 * x`) und bekommt das Statement mit dem Ergebnis geprintet, wobei das Ergebnis (als Wert, nicht Referenz) auch zurückgegeben wird.
== Funktionen in Structs
Unser Struct soll jetzt auch eine Funktion auf sich selbst aufrufen können.
Tatsächlich ist der sehr einfach und sehr OOPig.
Die folgenden Beispiele sollten relativ viel erklären:
[source, rust]
----
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Das ist eine Methode/"method"
// Erster Parameter ist &self (/&mut self) und wird aufgerufen wie folgt:
// var.area();
fn area(&self) -> u32 {
self.width * self.height
}
// Das ist eine "associated function"
// Kein &self und aufgerufen wie folgt:
// Rectangle::square(5);
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
// Mehrere impl Blöcke sind erlaubt
impl Rectangle {
// var.has_same_area(&other);
fn has_same_area(&self, other: &Rectangle) -> bool {
self.area() == other.area()
}
// Rectangle::same_area(&first, &second);
fn same_area(first: &Rectangle, second: &Rectangle) -> bool {
first.area() == second.area()
}
// Methoden können auch wie Felder heißen
fn width(&self) -> bool {
self.width > 0
}
}
fn main() {
let rect1 = Rectangle {
width: 12,
height: 3,
};
let rect2 = Rectangle::square(6);
println!("{}", rect1.area()); // 36
println!("{}", rect2.area()); // 36
println!("{}", rect1.has_same_area(&rect2)); // true
println!("{}", rect2.has_same_area(&rect1)); // true
println!("{}", Rectangle::same_area(&rect1, &rect2)); // true
}
----
=== `&mut self`
Eine Methode kann auch `&mut self` als ersten Parameter haben.
Dann können auch Felder geschrieben werden. In diesem Fall werden Referenzen aber invalidiert!
[source, rust]
----
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn change_width(&mut self, width: u32) {
self.width = width;
}
}
fn main() {
let mut rect1 = Rectangle {
width: 12,
height: 3,
};
let ref1 = &rect1;
rect1.change_width(5);
println!("{}", ref1.width); // <- geht nicht!
}
----